#pragma once

#include <type_traits>

#include "../base/types.h"
#include "../base/bits.h"

OPEN_JLIB_NS


template<typename Type, Type Poly, Type Init = 0, Type Xor = 0, bool Rev = false, u8 Width = sizeof(Type) * 8>
struct CRC {

  static_assert(std::is_integral<Type>::value, "");
  static_assert(0 < Width && Width <= sizeof(Type) * 8, "");

  using Tu = typename std::make_unsigned<Type>::type;


  template<typename T = Type, T P = Poly << (8 - Width), std::enable_if_t<std::is_integral<T>::value && Width < 8 && !Rev, int> = 0>
  constexpr static Tu _ (u8 i) noexcept {
    Tu a = (Tu)i;
    Tu b = (Tu)0x80;
    for (auto j = 0; j < 8; ++j) {
      Tu s = a & b;
      a = a << 1;
      a = s ? a ^ P : a;
    }
    return a;
  }
  template<typename T = Type, u8 S = sizeof(u8) * 8 - Width, std::enable_if_t<std::is_integral<T>::value && Width < 8 && !Rev, int> = 0>
  inline static Tu _update (const Tu tbl[256], const u8 *src, uwl src_len, Tu init) noexcept {
    Tu a = (Tu)(init << S);
    for (auto sp = src, ep = sp + src_len; sp < ep; ++sp)
      a = tbl[(u8)a ^ *sp];
    return a >> S;
  }


  template<typename T = Type, u8 B = Width, T P = Poly, std::enable_if_t<std::is_integral<T>::value && Width >= 8 && !Rev, int> = 0>
  constexpr static Tu _ (u8 i) noexcept {
    Tu a = (Tu)i << (B - 8);
    Tu b = (Tu)(1ULL << (B - 1));
    for (auto j = 0; j < 8; ++j) {
      T s = a & b;
      a = a << 1;
      a = s ? a ^ P : a;
    }
    return B == sizeof(T) * 8 ? a : a & (Tu)((1ULL << B) - 1);
  }
  template<typename T = Type, u8 B = Width, std::enable_if_t<std::is_integral<T>::value && Width >= 8 && !Rev, int> = 0>
  inline static Tu _update (const Tu tbl[256], const u8 *src, uwl src_len, Tu init) noexcept {
    Tu a = init;
    for (auto sp = src, ep = sp + src_len; sp < ep; ++sp) {
      u8 i = (u8)(a >> (B - 8)) ^ *sp;
      a = (a << 8) ^ tbl[i];
    }
    return B == sizeof(T) * 8 ? a : a & (Tu)((1ULL << B) - 1);
  }


  template<typename T = Type, u8 B = Width, T P = (Bits<T>::reverse(Poly) >> (sizeof(T) * 8 - Width)), std::enable_if_t<std::is_integral<T>::value && Rev, int> = 0>
  constexpr static Tu _ (u8 i) noexcept {
    Tu a = (Tu)i;
    for (auto j = 0; j < 8; ++j) {
      Tu s = a & (Tu)1;
      a = a >> 1;
      a = s ? a ^ P : a;
    }
    return B == sizeof(T) * 8 ? a : a & (Tu)((1ULL << B) - 1);
  }
  template<typename T = Type, u8 B = Width, std::enable_if_t<std::is_integral<T>::value && Width < 8 && Rev, int> = 0>
  inline static Tu _update (const Tu tbl[256], const u8 *src, uwl src_len, Tu init) noexcept {
    Tu a = Bits<Tu>::reverse(init) >> (sizeof(T) * 8 - B);
    for (auto sp = src, ep = sp + src_len; sp < ep; ++sp)
      a = tbl[(u8)a ^ *sp];
    return a;
  }
  template<typename T = Type, u8 B = Width, std::enable_if_t<std::is_integral<T>::value && Width >= 8 && Rev, int> = 0>
  inline static Tu _update (const Tu tbl[256], const u8 *src, uwl src_len, Tu init) noexcept {
    Tu a = Bits<Tu>::reverse(init) >> (sizeof(T) * 8 - B);
    for (auto sp = src, ep = sp + src_len; sp < ep; ++sp) {
      u8 i = (u8)a ^ *sp;
      a = (a >> 8) ^ tbl[i];
    }
    return a;
  }


public:
  static Tu update (const void *src, uwl src_len, Tu init = 0) noexcept {
    static const Tu tbl[] = {
      _(0x00), _(0x01), _(0x02), _(0x03), _(0x04), _(0x05), _(0x06), _(0x07),
      _(0x08), _(0x09), _(0x0A), _(0x0B), _(0x0C), _(0x0D), _(0x0E), _(0x0F),
      _(0x10), _(0x11), _(0x12), _(0x13), _(0x14), _(0x15), _(0x16), _(0x17),
      _(0x18), _(0x19), _(0x1A), _(0x1B), _(0x1C), _(0x1D), _(0x1E), _(0x1F),
      _(0x20), _(0x21), _(0x22), _(0x23), _(0x24), _(0x25), _(0x26), _(0x27),
      _(0x28), _(0x29), _(0x2A), _(0x2B), _(0x2C), _(0x2D), _(0x2E), _(0x2F),
      _(0x30), _(0x31), _(0x32), _(0x33), _(0x34), _(0x35), _(0x36), _(0x37),
      _(0x38), _(0x39), _(0x3A), _(0x3B), _(0x3C), _(0x3D), _(0x3E), _(0x3F),
      _(0x40), _(0x41), _(0x42), _(0x43), _(0x44), _(0x45), _(0x46), _(0x47),
      _(0x48), _(0x49), _(0x4A), _(0x4B), _(0x4C), _(0x4D), _(0x4E), _(0x4F),
      _(0x50), _(0x51), _(0x52), _(0x53), _(0x54), _(0x55), _(0x56), _(0x57),
      _(0x58), _(0x59), _(0x5A), _(0x5B), _(0x5C), _(0x5D), _(0x5E), _(0x5F),
      _(0x60), _(0x61), _(0x62), _(0x63), _(0x64), _(0x65), _(0x66), _(0x67),
      _(0x68), _(0x69), _(0x6A), _(0x6B), _(0x6C), _(0x6D), _(0x6E), _(0x6F),
      _(0x70), _(0x71), _(0x72), _(0x73), _(0x74), _(0x75), _(0x76), _(0x77),
      _(0x78), _(0x79), _(0x7A), _(0x7B), _(0x7C), _(0x7D), _(0x7E), _(0x7F),
      _(0x80), _(0x81), _(0x82), _(0x83), _(0x84), _(0x85), _(0x86), _(0x87),
      _(0x88), _(0x89), _(0x8A), _(0x8B), _(0x8C), _(0x8D), _(0x8E), _(0x8F),
      _(0x90), _(0x91), _(0x92), _(0x93), _(0x94), _(0x95), _(0x96), _(0x97),
      _(0x98), _(0x99), _(0x9A), _(0x9B), _(0x9C), _(0x9D), _(0x9E), _(0x9F),
      _(0xA0), _(0xA1), _(0xA2), _(0xA3), _(0xA4), _(0xA5), _(0xA6), _(0xA7),
      _(0xA8), _(0xA9), _(0xAA), _(0xAB), _(0xAC), _(0xAD), _(0xAE), _(0xAF),
      _(0xB0), _(0xB1), _(0xB2), _(0xB3), _(0xB4), _(0xB5), _(0xB6), _(0xB7),
      _(0xB8), _(0xB9), _(0xBA), _(0xBB), _(0xBC), _(0xBD), _(0xBE), _(0xBF),
      _(0xC0), _(0xC1), _(0xC2), _(0xC3), _(0xC4), _(0xC5), _(0xC6), _(0xC7),
      _(0xC8), _(0xC9), _(0xCA), _(0xCB), _(0xCC), _(0xCD), _(0xCE), _(0xCF),
      _(0xD0), _(0xD1), _(0xD2), _(0xD3), _(0xD4), _(0xD5), _(0xD6), _(0xD7),
      _(0xD8), _(0xD9), _(0xDA), _(0xDB), _(0xDC), _(0xDD), _(0xDE), _(0xDF),
      _(0xE0), _(0xE1), _(0xE2), _(0xE3), _(0xE4), _(0xE5), _(0xE6), _(0xE7),
      _(0xE8), _(0xE9), _(0xEA), _(0xEB), _(0xEC), _(0xED), _(0xEE), _(0xEF),
      _(0xF0), _(0xF1), _(0xF2), _(0xF3), _(0xF4), _(0xF5), _(0xF6), _(0xF7),
      _(0xF8), _(0xF9), _(0xFA), _(0xFB), _(0xFC), _(0xFD), _(0xFE), _(0xFF),
    };
    return _update(tbl, (const u8*)src, src_len, init);
  }
  
  
  inline static Type checksum (const void *src, uwl src_len) noexcept {
    return update(src, src_len, Init) ^ Xor;
  }
};


CLOSE_JLIB_NS
